# BDline.py — 完整可运行版本（包含导出 Excel 与精度评估）
from enum import Enum
import math
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import pandas as pd  # 用于 Excel 导出


# ===============================================================
# 【模块 1】二维网格与坐标编码定义
# ===============================================================
class BD_2D_LEVELS_SIZE(Enum):
    Level_2D_1 = [6, 4]  # 6°*4°
    Level_2D_2 = [30 / 60, 30 / 60]  # 30'*30'
    Level_2D_3 = [15 / 60, 10 / 60]  # 15'*10'
    Level_2D_4 = [1 / 60, 1 / 60]  # 1'*1'
    Level_2D_5 = [4 / 3600, 4 / 3600]  # 4''*4''
    Level_2D_6 = [2 / 3600, 2 / 3600]  # 2''*2''
    Level_2D_7 = [1 / 14400, 1 / 14400]  # 1/4''*1/4''
    Level_2D_8 = [1 / 115200, 1 / 115200]  # 1/32''*1/32''
    Level_2D_9 = [1 / 921600, 1 / 921600]  # 1/256''*1/256''
    Level_2D_10 = [1 / 7372800, 1 / 7372800]  # 1/2048''*1/2048''


class BD_HEMISPHERE(Enum):
    NORTH = 'N'
    SOUTH = 'S'


# ===============================================================
# 【模块 2】网格码结构定义
# ===============================================================
class BD2D_Code:
    def __init__(self, codeStr):
        self.sphere = codeStr[0]
        self.L1 = codeStr[1:4]
        self.L2 = codeStr[4:6]
        self.L3 = codeStr[6]
        self.L4 = codeStr[7:9]
        self.L5 = codeStr[9:11]
        self.L6 = codeStr[11]
        self.L7 = codeStr[12:14]
        self.L8 = codeStr[14:16]
        self.L9 = codeStr[16:18]
        self.L10 = codeStr[18:20]


# ===============================================================
# 【模块 3】经纬度 ↔ 网格码 转换类（完整实现）
# ===============================================================
class TransBD:
    _bd2d_level_ends = [4, 6, 7, 9, 11, 12, 14, 16, 18, 20]

    def __init__(self):
        self.r = 6378137

    def coordinates_to_bd2d_level(self, lat, lon, level: int):  # 点转指定级别网格码
        if not (1 <= level <= 10):
            raise ValueError("level must be in 1..10")
        full = self.coordinates_to_bd2d(lat, lon)
        end = self._bd2d_level_ends[level - 1]  # 索引是从0开始，所以1级对应的是索引“0”对应的
        return full[:end]  # 从全位网格码截取指定的级别

    def bd2d_to_coordinates_level(self, bd2d_code: str, level: int):
        """
        将网格码解码为指定级别的格网左下角坐标（返回 (lat_min, lon_min)）
        """
        if not (1 <= level <= 10):
            raise ValueError("level must be in 1..10")
        expected_len = self._bd2d_level_ends[level - 1]
        if len(bd2d_code) < expected_len:
            raise ValueError(f"bd2d_code length must be at least {expected_len} for level {level}")
        code = bd2d_code[:expected_len]

        sphere = code[0]
        L1 = code[1:4]
        L1_lon_corner = (int(L1[0:2]) - 31) * BD_2D_LEVELS_SIZE.Level_2D_1.value[0]
        L1_lat_corner = (ord(L1[2]) - ord("A")) * BD_2D_LEVELS_SIZE.Level_2D_1.value[1]
        if sphere == BD_HEMISPHERE.SOUTH.value:
            L1_lat_corner = -L1_lat_corner - BD_2D_LEVELS_SIZE.Level_2D_1.value[1]

        if level == 1:
            return (L1_lat_corner, L1_lon_corner)

        L2 = code[4:6]
        if L1_lon_corner >= 0:
            L2_lon_corner = L1_lon_corner + int(L2[0], 16) * BD_2D_LEVELS_SIZE.Level_2D_2.value[0]
        else:
            L2_lon_corner = L1_lon_corner + BD_2D_LEVELS_SIZE.Level_2D_1.value[0] - int(L2[0], 16) * \
                            BD_2D_LEVELS_SIZE.Level_2D_2.value[0]
        if sphere == BD_HEMISPHERE.NORTH.value:
            L2_lat_corner = L1_lat_corner + int(L2[1]) * BD_2D_LEVELS_SIZE.Level_2D_2.value[1]
        else:
            L2_lat_corner = L1_lat_corner + BD_2D_LEVELS_SIZE.Level_2D_1.value[1] - int(L2[1]) * \
                            BD_2D_LEVELS_SIZE.Level_2D_2.value[1]

        if level == 2:
            return (L2_lat_corner, L2_lon_corner)

        L3 = code[6]
        if L2_lon_corner >= 0:
            L3_lon_corner = L2_lon_corner + (int(L3) % 2) * BD_2D_LEVELS_SIZE.Level_2D_3.value[0]
        else:
            L3_lon_corner = L2_lon_corner - (int(L3) % 2) * BD_2D_LEVELS_SIZE.Level_2D_3.value[0]
        if sphere == BD_HEMISPHERE.NORTH.value:
            L3_lat_corner = L2_lat_corner + (int(L3) // 2) * BD_2D_LEVELS_SIZE.Level_2D_3.value[1]
        else:
            L3_lat_corner = L2_lat_corner - (int(L3) // 2) * BD_2D_LEVELS_SIZE.Level_2D_3.value[1]

        if level == 3:
            return (L3_lat_corner, L3_lon_corner)

        L4 = code[7:9]
        if L3_lon_corner >= 0:
            L4_lon_corner = L3_lon_corner + int(L4[0], 16) * BD_2D_LEVELS_SIZE.Level_2D_4.value[0]
        else:
            L4_lon_corner = L3_lon_corner - int(L4[0], 16) * BD_2D_LEVELS_SIZE.Level_2D_4.value[0]
        if sphere == BD_HEMISPHERE.NORTH.value:
            L4_lat_corner = L3_lat_corner + int(L4[1]) * BD_2D_LEVELS_SIZE.Level_2D_4.value[1]
        else:
            L4_lat_corner = L3_lat_corner - int(L4[1]) * BD_2D_LEVELS_SIZE.Level_2D_4.value[1]

        if level == 4:
            return (L4_lat_corner, L4_lon_corner)

        L5 = code[9:11]
        if L4_lon_corner >= 0:
            L5_lon_corner = L4_lon_corner + int(L5[0], 16) * BD_2D_LEVELS_SIZE.Level_2D_5.value[0]
        else:
            L5_lon_corner = L4_lon_corner - int(L5[0], 16) * BD_2D_LEVELS_SIZE.Level_2D_5.value[0]
        if sphere == BD_HEMISPHERE.NORTH.value:
            L5_lat_corner = L4_lat_corner + int(L5[1], 16) * BD_2D_LEVELS_SIZE.Level_2D_5.value[1]
        else:
            L5_lat_corner = L4_lat_corner - int(L5[1], 16) * BD_2D_LEVELS_SIZE.Level_2D_5.value[1]

        if level == 5:
            return (L5_lat_corner, L5_lon_corner)

        L6 = code[11]
        if L5_lon_corner >= 0:
            L6_lon_corner = L5_lon_corner + (int(L6) % 2) * BD_2D_LEVELS_SIZE.Level_2D_6.value[0]
        else:
            L6_lon_corner = L5_lon_corner - (int(L6) % 2) * BD_2D_LEVELS_SIZE.Level_2D_6.value[0]
        if sphere == BD_HEMISPHERE.NORTH.value:
            L6_lat_corner = L5_lat_corner + (int(L6) // 2) * BD_2D_LEVELS_SIZE.Level_2D_6.value[1]
        else:
            L6_lat_corner = L5_lat_corner - (int(L6) // 2) * BD_2D_LEVELS_SIZE.Level_2D_6.value[1]

        if level == 6:
            return (L6_lat_corner, L6_lon_corner)

        L7 = code[12:14]
        if L6_lon_corner >= 0:
            L7_lon_corner = L6_lon_corner + int(L7[0]) * BD_2D_LEVELS_SIZE.Level_2D_7.value[0]
        else:
            L7_lon_corner = L6_lon_corner - int(L7[0]) * BD_2D_LEVELS_SIZE.Level_2D_7.value[0]
        if sphere == BD_HEMISPHERE.NORTH.value:
            L7_lat_corner = L6_lat_corner + int(L7[1]) * BD_2D_LEVELS_SIZE.Level_2D_7.value[1]
        else:
            L7_lat_corner = L6_lat_corner - int(L7[1]) * BD_2D_LEVELS_SIZE.Level_2D_7.value[1]

        if level == 7:
            return (L7_lat_corner, L7_lon_corner)

        L8 = code[14:16]
        if L7_lon_corner >= 0:
            L8_lon_corner = L7_lon_corner + int(L8[0]) * BD_2D_LEVELS_SIZE.Level_2D_8.value[0]
        else:
            L8_lon_corner = L7_lon_corner - int(L8[0]) * BD_2D_LEVELS_SIZE.Level_2D_8.value[0]
        if sphere == BD_HEMISPHERE.NORTH.value:
            L8_lat_corner = L7_lat_corner + int(L8[1]) * BD_2D_LEVELS_SIZE.Level_2D_8.value[1]
        else:
            L8_lat_corner = L7_lat_corner - int(L8[1]) * BD_2D_LEVELS_SIZE.Level_2D_8.value[1]

        if level == 8:
            return (L8_lat_corner, L8_lon_corner)

        L9 = code[16:18]
        if L8_lon_corner >= 0:
            L9_lon_corner = L8_lon_corner + int(L9[0]) * BD_2D_LEVELS_SIZE.Level_2D_9.value[0]
        else:
            L9_lon_corner = L8_lon_corner - int(L9[0]) * BD_2D_LEVELS_SIZE.Level_2D_9.value[0]
        if sphere == BD_HEMISPHERE.NORTH.value:
            L9_lat_corner = L8_lat_corner + int(L9[1]) * BD_2D_LEVELS_SIZE.Level_2D_9.value[1]
        else:
            L9_lat_corner = L8_lat_corner - int(L9[1]) * BD_2D_LEVELS_SIZE.Level_2D_9.value[1]

        if level == 9:
            return (L9_lat_corner, L9_lon_corner)

        L10 = code[18:20]
        if L9_lon_corner >= 0:
            L10_lon_corner = L9_lon_corner + int(L10[0]) * BD_2D_LEVELS_SIZE.Level_2D_10.value[0]
        else:
            L10_lon_corner = L9_lon_corner - int(L10[0]) * BD_2D_LEVELS_SIZE.Level_2D_10.value[0]
        if sphere == BD_HEMISPHERE.NORTH.value:
            L10_lat_corner = L9_lat_corner + int(L10[1]) * BD_2D_LEVELS_SIZE.Level_2D_10.value[1]
        else:
            L10_lat_corner = L9_lat_corner - int(L10[1]) * BD_2D_LEVELS_SIZE.Level_2D_10.value[1]

        return (L10_lat_corner, L10_lon_corner)

    def coordinates_to_bd2d(self, lat, lon):
        """
        将经纬度转换为完整 10 级网格码（返回长度 20 的字符串）
        """
        # --- 新增：用于处理南纬恰好落在格高整数倍的边界点 ---
        eps = 1e-12
        # 当前一级格高（纬向）
        d_lat1 = BD_2D_LEVELS_SIZE.Level_2D_1.value[1]

        # 主要解决南纬边界值点问题
        if lat < 0 and math.isclose(abs(lat) % d_lat1, 0.0, abs_tol=1e-12):
            lat += eps

        sphere = BD_HEMISPHERE.NORTH if lat >= 0 else BD_HEMISPHERE.SOUTH
        L1_lon_code = str(int(lon // BD_2D_LEVELS_SIZE.Level_2D_1.value[0] + 31)).zfill(2)
        L1_lat_code = chr(int(abs(lat) // BD_2D_LEVELS_SIZE.Level_2D_1.value[1]) + ord("A"))
        L1_code = L1_lon_code + L1_lat_code

        if lon >= 0:
            L1_lon_corner = (int(L1_lon_code) - 31) * BD_2D_LEVELS_SIZE.Level_2D_1.value[0]
        else:
            L1_lon_corner = (int(L1_lon_code) + 1 - 31) * BD_2D_LEVELS_SIZE.Level_2D_1.value[0]
        if lat >= 0:
            L1_lat_corner = (ord(L1_lat_code) - ord("A")) * BD_2D_LEVELS_SIZE.Level_2D_1.value[1]
        else:
            L1_lat_corner = -(ord(L1_lat_code) - ord("A")) * BD_2D_LEVELS_SIZE.Level_2D_1.value[1]  # 可能有问题

        L2_lon_index = int(abs(lon - L1_lon_corner) // BD_2D_LEVELS_SIZE.Level_2D_2.value[0])
        L2_lat_index = int(abs(lat - L1_lat_corner) // BD_2D_LEVELS_SIZE.Level_2D_2.value[1])
        L2_code = f"{L2_lon_index:X}{L2_lat_index}"

        if lon >= 0:
            L2_lon_conner = L1_lon_corner + L2_lon_index * BD_2D_LEVELS_SIZE.Level_2D_2.value[0]
        else:
            L2_lon_conner = L1_lon_corner - L2_lon_index * BD_2D_LEVELS_SIZE.Level_2D_2.value[0]
        if lat >= 0:
            L2_lat_conner = L1_lat_corner + L2_lat_index * BD_2D_LEVELS_SIZE.Level_2D_2.value[1]
        else:
            L2_lat_conner = L1_lat_corner - L2_lat_index * BD_2D_LEVELS_SIZE.Level_2D_2.value[1]

        L3_index = abs(lat - L2_lat_conner) // BD_2D_LEVELS_SIZE.Level_2D_3.value[1] * 2 + abs(
            lon - L2_lon_conner) // BD_2D_LEVELS_SIZE.Level_2D_3.value[0]
        L3_code = str(int(L3_index)).zfill(1)

        if lon >= 0:
            L3_lon_conner = L2_lon_conner + (L3_index % 2) * BD_2D_LEVELS_SIZE.Level_2D_3.value[0]
        else:
            L3_lon_conner = L2_lon_conner - (L3_index % 2) * BD_2D_LEVELS_SIZE.Level_2D_3.value[0]
        if lat >= 0:
            L3_lat_conner = L2_lat_conner + (L3_index // 2) * BD_2D_LEVELS_SIZE.Level_2D_3.value[1]
        else:
            L3_lat_conner = L2_lat_conner - (L3_index // 2) * BD_2D_LEVELS_SIZE.Level_2D_3.value[1]

        L4_lon_index = int(abs(lon - L3_lon_conner) // BD_2D_LEVELS_SIZE.Level_2D_4.value[0])
        L4_lat_index = int(abs(lat - L3_lat_conner) // BD_2D_LEVELS_SIZE.Level_2D_4.value[1])
        L4_code = f"{L4_lon_index:X}{L4_lat_index}"

        if lon >= 0:
            L4_lon_conner = L3_lon_conner + L4_lon_index * BD_2D_LEVELS_SIZE.Level_2D_4.value[0]
        else:
            L4_lon_conner = L3_lon_conner - L4_lon_index * BD_2D_LEVELS_SIZE.Level_2D_4.value[0]
        if lat >= 0:
            L4_lat_conner = L3_lat_conner + L4_lat_index * BD_2D_LEVELS_SIZE.Level_2D_4.value[1]
        else:
            L4_lat_conner = L3_lat_conner - L4_lat_index * BD_2D_LEVELS_SIZE.Level_2D_4.value[1]

        L5_lon_index = int(abs(lon - L4_lon_conner) // BD_2D_LEVELS_SIZE.Level_2D_5.value[0])
        L5_lat_index = int(abs(lat - L4_lat_conner) // BD_2D_LEVELS_SIZE.Level_2D_5.value[1])
        L5_code = f"{L5_lon_index:X}{L5_lat_index:X}"

        if lon >= 0:
            L5_lon_conner = L4_lon_conner + L5_lon_index * BD_2D_LEVELS_SIZE.Level_2D_5.value[0]
        else:
            L5_lon_conner = L4_lon_conner - L5_lon_index * BD_2D_LEVELS_SIZE.Level_2D_5.value[0]
        if lat >= 0:
            L5_lat_conner = L4_lat_conner + L5_lat_index * BD_2D_LEVELS_SIZE.Level_2D_5.value[1]
        else:
            L5_lat_conner = L4_lat_conner - L5_lat_index * BD_2D_LEVELS_SIZE.Level_2D_5.value[1]

        L6_index = abs(lat - L5_lat_conner) // BD_2D_LEVELS_SIZE.Level_2D_6.value[1] * 2 + abs(
            lon - L5_lon_conner) // BD_2D_LEVELS_SIZE.Level_2D_6.value[0]
        L6_code = str(int(L6_index)).zfill(1)

        if lon >= 0:
            L6_lon_conner = L5_lon_conner + (L6_index % 2) * BD_2D_LEVELS_SIZE.Level_2D_6.value[0]
        else:
            L6_lon_conner = L5_lon_conner - (L6_index % 2) * BD_2D_LEVELS_SIZE.Level_2D_6.value[0]
        if lat >= 0:
            L6_lat_conner = L5_lat_conner + (L6_index // 2) * BD_2D_LEVELS_SIZE.Level_2D_6.value[1]
        else:
            L6_lat_conner = L5_lat_conner - (L6_index // 2) * BD_2D_LEVELS_SIZE.Level_2D_6.value[1]

        L7_lon_index = int(abs(lon - L6_lon_conner) // BD_2D_LEVELS_SIZE.Level_2D_7.value[0])
        L7_lat_index = int(abs(lat - L6_lat_conner) // BD_2D_LEVELS_SIZE.Level_2D_7.value[1])
        L7_code = f"{L7_lon_index}{L7_lat_index}"

        if lon >= 0:
            L7_lon_conner = L6_lon_conner + L7_lon_index * BD_2D_LEVELS_SIZE.Level_2D_7.value[0]
        else:
            L7_lon_conner = L6_lon_conner - L7_lon_index * BD_2D_LEVELS_SIZE.Level_2D_7.value[0]
        if lat >= 0:
            L7_lat_conner = L6_lat_conner + L7_lat_index * BD_2D_LEVELS_SIZE.Level_2D_7.value[1]
        else:
            L7_lat_conner = L6_lat_conner - L7_lat_index * BD_2D_LEVELS_SIZE.Level_2D_7.value[1]

        L8_lon_index = int(abs(lon - L7_lon_conner) // BD_2D_LEVELS_SIZE.Level_2D_8.value[0])
        L8_lat_index = int(abs(lat - L7_lat_conner) // BD_2D_LEVELS_SIZE.Level_2D_8.value[1])
        L8_code = f"{L8_lon_index}{L8_lat_index}"

        if lon >= 0:
            L8_lon_conner = L7_lon_conner + L8_lon_index * BD_2D_LEVELS_SIZE.Level_2D_8.value[0]
        else:
            L8_lon_conner = L7_lon_conner - L8_lon_index * BD_2D_LEVELS_SIZE.Level_2D_8.value[0]
        if lat >= 0:
            L8_lat_conner = L7_lat_conner + L8_lat_index * BD_2D_LEVELS_SIZE.Level_2D_8.value[1]
        else:
            L8_lat_conner = L7_lat_conner - L8_lat_index * BD_2D_LEVELS_SIZE.Level_2D_8.value[1]

        L9_lon_index = int(abs(lon - L8_lon_conner) // BD_2D_LEVELS_SIZE.Level_2D_9.value[0])
        L9_lat_index = int(abs(lat - L8_lat_conner) // BD_2D_LEVELS_SIZE.Level_2D_9.value[1])
        L9_code = f"{L9_lon_index}{L9_lat_index}"

        if lon >= 0:
            L9_lon_conner = L8_lon_conner + L9_lon_index * BD_2D_LEVELS_SIZE.Level_2D_9.value[0]
        else:
            L9_lon_conner = L8_lon_conner - L9_lon_index * BD_2D_LEVELS_SIZE.Level_2D_9.value[0]
        if lat >= 0:
            L9_lat_conner = L8_lat_conner + L9_lat_index * BD_2D_LEVELS_SIZE.Level_2D_9.value[1]
        else:
            L9_lat_conner = L8_lat_conner - L9_lat_index * BD_2D_LEVELS_SIZE.Level_2D_9.value[1]

        L10_lon_index = int(abs(lon - L9_lon_conner) // BD_2D_LEVELS_SIZE.Level_2D_10.value[0])
        L10_lat_index = int(abs(lat - L9_lat_conner) // BD_2D_LEVELS_SIZE.Level_2D_10.value[1])
        L10_code = f"{L10_lon_index}{L10_lat_index}"

        return sphere.value + L1_code + L2_code + L3_code + L4_code + L5_code + L6_code + L7_code + L8_code + L9_code + L10_code


# ===============================================================
# 【模块 4】线对象编码与可视化类
# ===============================================================
class BDLineEncoder:
    def __init__(self, trans_bd_instance):
        self.trans = trans_bd_instance

    def _get_grids_between_points(self, lat1, lon1, lat2, lon2, level):
        """
        获取两点之间线段经过的所有网格（自适应采样）
        """
        grids = []  # 存储按顺序遇到的网格码
        seen = set()  # 快速判断某个网格是否已经记录过

        # 获取当前level的网格尺寸（value 顺序： [lon, lat]）
        try:
            d_lon, d_lat = getattr(BD_2D_LEVELS_SIZE, f"Level_2D_{level}").value  # d_lon当前级别下，一个格子的经度宽度
        except AttributeError:
            raise ValueError(f"Invalid level: {level}")

        delta_lat = abs(lat2 - lat1)  # 纬度差
        delta_lon = abs(lon2 - lon1)  # 精度差

        max_grids_lat = int(delta_lat / d_lat) + 2  # 判断穿过的格子数（+2是把边界也算上了）
        max_grids_lon = int(delta_lon / d_lon) + 2

        num_samples = max(10, (max_grids_lat + max_grids_lon) * 3)  # “*3”平均每格有 3 个采样点（画图解释更清晰）；10是最低采样数

        for i in range(num_samples + 1):
            t = i / num_samples  # 均匀采样，适用于直线段
            lat = lat1 + t * (lat2 - lat1)  # 采样点的经纬度
            lon = lon1 + t * (lon2 - lon1)

            code = self.trans.coordinates_to_bd2d_level(lat, lon, level)  # 得到经过格子的网格码
            if code not in seen:
                grids.append(code)  # 存放
                seen.add(code)  # 防止重复

        return grids

    def line_to_bd2d_sequence(self, line_coords, level=8):  # 将会反复调用上一个函数，默认8级
        """
        将线对象转换为完整的BD2D网格码序列（包含所有经过的网格）
        """
        if len(line_coords) < 2:  # 线段必须两个点
            raise ValueError("Line must have at least 2 points")

        all_codes = []  # 存所有遇到的网格码（按顺序）
        seen = set()  # 快速判断某个网格是否已经记录过

        for i in range(len(line_coords) - 1):  # 比如5个坐标，那么就需要4个点去间隔
            lat1, lon1 = line_coords[i]
            lat2, lon2 = line_coords[i + 1]

            segment_grids = self._get_grids_between_points(
                lat1, lon1, lat2, lon2, level
            )  # 这样就可以把4个线段经过的格子提取出来

            for code in segment_grids:  # 遍历所格子，若没有见到过就保留，见到就跳过
                if code not in seen:
                    all_codes.append(code)
                    seen.add(code)

        return tuple(all_codes)

    def bd2d_sequence_to_coordinates(self, bd2d_tuple, level=8,
                                     corner='corner'):  # 默认8级，'corner'取corner（左下角）、center（中心）
        """
        将BD2D网格码序列还原为地理坐标序列
        """
        coords = []
        for code in bd2d_tuple:
            lat_min, lon_min = self.trans.bd2d_to_coordinates_level(code, level)

            if corner == 'center':
                try:
                    d_lon, d_lat = getattr(BD_2D_LEVELS_SIZE, f"Level_2D_{level}").value  # 上面有这个函数
                except AttributeError:
                    raise ValueError(f"Invalid level: {level}")
                lat_center = lat_min + d_lat / 2
                lon_center = lon_min + d_lon / 2
                coords.append((lat_center, lon_center))
            else:
                coords.append((lat_min, lon_min))

        return coords

    def visualize_line_on_grid(self, original_line, bd2d_tuple, level=8):  # 默认8级，‘original_line’输入的经纬度坐标
        """
        可视化原始线段和网格码序列
        """
        grid_coords = self.bd2d_sequence_to_coordinates(bd2d_tuple, level, corner='corner')  # 求每个网格码的左下角坐标

        orig_lats = [c[0] for c in original_line]  # 从输入的坐标中，提取所有纬度，因为输入格式为（纬度，精度）
        orig_lons = [c[1] for c in original_line]

        try:  # 获取网格级别尺寸
            d_lon, d_lat = getattr(BD_2D_LEVELS_SIZE, f"Level_2D_{level}").value
        except AttributeError:
            raise ValueError(f"Unsupported level: {level}")

        fig, ax = plt.subplots(figsize=(16, 10))  # 创建一个ax的画布

        # 绘制坐标轨迹图
        ax.plot(orig_lons, orig_lats, color='red', linewidth=3,
                marker='o', markersize=10, label='Original Line', zorder=4,
                markerfacecolor='red', markeredgecolor='darkred', markeredgewidth=2)

        # 绘制每个网格
        for idx, (lat_min, lon_min) in enumerate(grid_coords):  # 遍历每个格子的左下角
            rect = Rectangle(
                (lon_min, lat_min), d_lon, d_lat,
                linewidth=1.5, edgecolor='green', facecolor='lightgreen',
                alpha=0.3, zorder=1
            )
            ax.add_patch(rect)  # 画矩形

        # 画出网格中心连成的路径（蓝色虚线）
        grid_centers_lat = [c[0] + d_lat / 2 for c in grid_coords]
        grid_centers_lon = [c[1] + d_lon / 2 for c in grid_coords]
        ax.plot(grid_centers_lon, grid_centers_lat, color='blue', linewidth=2,
                marker='x', markersize=6, alpha=0.7, label='Grid Path', zorder=3,
                linestyle='--')

        # 设置坐标轴标签,标题显示:当前级别、网格大小、总共用了多少个格子
        ax.set_xlabel("Longitude (°)", fontsize=13, fontweight='bold')
        ax.set_ylabel("Latitude (°)", fontsize=13, fontweight='bold')
        ax.set_title(f"BD2D Grid Line Representation (Level {level})\n"
                     f"Grid Size: {d_lat:.6f}° × {d_lon:.6f}° | Total Grids: {len(bd2d_tuple)}",
                     fontsize=14, fontweight='bold')
        ax.legend(fontsize=12, loc='best')
        ax.grid(True, alpha=0.3, linestyle='--', linewidth=0.5)

        # 设置坐标轴范围和比例
        lat_margin = (max(orig_lats) - min(orig_lats)) * 0.1
        lon_margin = (max(orig_lons) - min(orig_lons)) * 0.1
        ax.set_xlim(min(orig_lons) - lon_margin, max(orig_lons) + lon_margin)
        ax.set_ylim(min(orig_lats) - lat_margin, max(orig_lats) + lat_margin)

        ax.set_aspect('equal', adjustable='box')  # 保证经纬度的缩放比例一致（否则地图会拉伸变形）
        plt.tight_layout()  # 自动调整布局，防止文字被截断
        plt.show()


# ===============================================================
# 【模块 5】任务一：导出指定级别网格码到 Excel（自动命名）
# ===============================================================
def export_bd2d_codes_to_excel(line_coords, encoder, levels=None, save_path=None):
    """
    任务一：将指定级别的网格码以列形式导出到 Excel 文件

    参数：
        line_coords : list[(lat, lon)]
        encoder : BDLineEncoder 实例
        levels : list[int] 可选（要导出的级别，例如 [1,2,5,6]）
        save_path : str 可选（若未指定，将自动命名如 bd2d_codes_1_2.xlsx）
    """
    if levels is None:
        levels = list(range(1, 11))
    levels = sorted(set(levels))

    if save_path is None:
        level_str = "_".join(map(str, levels))
        save_path = f"bd2d_codes_{level_str}.xlsx"

    data = {}
    for level in levels:
        print(f"正在生成第 {level} 级网格码...")
        bd2d_tuple = encoder.line_to_bd2d_sequence(line_coords, level=level)
        data[f"{level}级网格码"] = list(bd2d_tuple)

    max_len = max(len(v) for v in data.values())
    for k in data:
        data[k] += [""] * (max_len - len(data[k]))

    df = pd.DataFrame(data)
    df.to_excel(save_path, index=False)
    print(f"\n✅ 已导出网格码至：{save_path}\n")


# ===============================================================
# 【模块 6】任务二：网格码前缀一致性精度验证（优化版）
# ===============================================================
def evaluate_bd2d_precision(line_coords, encoder, levels=None):
    """
    任务二：评价不同级别网格码之间的前缀一致性（精度关系）

    参数：
        line_coords : list[(lat, lon)]
        encoder : BDLineEncoder 实例
        levels : list[int] 要对比的级别（如 [1,2] 或 [1,2,5,6]）
    """
    if levels is None:
        levels = [1, 2]

    levels = sorted(set(levels))
    level_codes = {}

    print(f"\n📊 正在计算网格码序列（级别：{levels}）...")
    for level in levels:
        print(f"  → 生成第 {level} 级网格码序列中...")
        level_codes[level] = encoder.line_to_bd2d_sequence(line_coords, level=level)
    print("✅ 所有指定级别网格码计算完成\n")

    print("📈 精度一致性评估结果")
    print("=" * 60)

    for i in range(1, len(levels)):
        lower = levels[i - 1]
        higher = levels[i]

        prefix_len = encoder.trans._bd2d_level_ends[lower - 1]
        lower_prefixes = set(code[:prefix_len] for code in level_codes[lower])
        higher_prefixes = set(code[:prefix_len] for code in level_codes[higher])
        new_prefixes = higher_prefixes - lower_prefixes

        print(f"Level {lower} → Level {higher}:")
        print(f"  下级前缀数: {len(lower_prefixes)}")
        print(f"  上级前缀数: {len(higher_prefixes)}")
        print(f"  新增(上级独有)前缀数: {len(new_prefixes)}")

        if new_prefixes:
            print(f"  ⚠️ 存在 {len(new_prefixes)} 个未在下级出现的前缀")
        else:
            print("  ✅ 前缀完全匹配")
        print("-" * 60)


# ===============================================================
# 【模块 7】主程序示例（整合任务一 + 任务二）
# ===============================================================
if __name__ == "__main__":
    # 创建TransBD实例
    trans = TransBD()

    # 创建编码器
    encoder = BDLineEncoder(trans)

    # 定义一条测试线段（模拟轨迹）
    line_coords = [
        (-4, 3),  # （纬度，经度）
        (-5, 5),
        (-8, 6),
        (-6, -6),
        (8, 6),
        (-3, 8),
    ]

    print("=" * 60)
    print("BD2D 完整网格编码测试")
    print("=" * 60)

    # 测试不同层级
    for test_level in [1]:  # 设置输出格网等级
        print(f"\n--- Level {test_level} ---")

        # 转为BD2D网格码序列（包含所有经过的网格）
        bd2d_tuple = encoder.line_to_bd2d_sequence(
            line_coords,
            level=test_level
        )

        print(f"Total grids traversed: {len(bd2d_tuple)}")
        """
        print(f"\nFirst 10 grid codes:")
        for i, code in enumerate(bd2d_tuple[:10]):
            print(f"  {i + 1}. {code}")        

        if len(bd2d_tuple) > 10:
            print(f"  ... (and {len(bd2d_tuple) - 10} more)")
        """
        print(f"Total grids traversed: {len(bd2d_tuple)}")
        print(f"\nAll {len(bd2d_tuple)} grid codes:")
        for i, code in enumerate(bd2d_tuple):
            print(f"  {i + 1}. {code}")

        # 可视化所有level
        print(f"\nVisualizing Level {test_level}...")
        encoder.visualize_line_on_grid(line_coords, bd2d_tuple, level=test_level)

    selected_levels = [1]  # 可改为你需要测试的级别组合

    # 导出 Excel（自动命名）
    # export_bd2d_codes_to_excel(line_coords, encoder, levels=selected_levels)

    # 精度评估
    # evaluate_bd2d_precision(line_coords, encoder, levels=selected_levels)

    print("\n🎯 所有任务执行完成！")
